﻿/*
 * Создан: Оникийчук Антон Игоревич
 * Компания: ООО "ИМСАТ"
 * Дата создания: 21.01.2009
 * Время создания: 10:12
 * Emal разработчика: onikiychuka@gmail.com
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Xml;
using IMSAT.OFTD.DOM.DataProxy;

namespace IMSAT.OFTD.DOM
{
    public class NonIndexAttribute : Attribute
    {
    }

    /// <summary>
    /// Узел DOM дерева
    /// </summary>
    public class Node : DependencyObject
    {
        #region Delegates

        public delegate void NodeChangedDelegate(Node node);

        public delegate void NodeDataAskedDelegate(Node node);

        #endregion

        private readonly NodeCollection<Node> _childNodes = new NodeCollection<Node>();

        private string _xmlName = "";

        public Node()
        {
            Changed += delegate { };
            DataAsked += delegate { };
        }

        public string XmlName
        {
            get { return _xmlName; }
        }

        /// <summary>
        /// Родительский узел дерева
        /// </summary>
        public Node Parent { get; set; }

        /// <summary>
        /// Дочерние узлы дерева
        /// </summary>
        public NodeCollection<Node> ChildNodes
        {
            get
            {
                OnDataAsked();
                return _childNodes;
            }
        }

        /// <summary>
        /// Имеет ли елемент дочерние узлы
        /// </summary>
        public bool HasChild
        {
            get
            {
                OnDataAsked();
                return (_childNodes.Count > 0);
            }
        }

        /// <summary>
        /// Добавить дочерний узел
        /// </summary>
        /// <param name="childNode">Узел</param>
        public Node AppendChild(Node childNode)
        {
            _childNodes.InsertChild(childNode);
            childNode.Changed += delegate(Node node) { Changed(node); };
            childNode.Parent = this;
            return this;
        }

        /// <summary>
        /// Удалить дочерний узел
        /// </summary>
        public void RemoveChild(Node childNode)
        {
            using (NodeCollection<Node>.NodeCollectionEditingFlag<Node> nef = _childNodes.BeginEdit())
            {
                childNode.RemoveChild(childNode);
            }
        }
        public NodeCollection<Node> Find(CheckNodeDelegate checkDelegate)
        {
            return Find(checkDelegate, true);
        }

        /// <summary>
        /// Найти все дочерние узлы(поиск происходит по всей части дерева
        /// начинающейся с данного элемента) соответсвующие условию
        /// </summary>
        /// <param name="checkDelegate">Условие</param>
        /// <returns>коллекция узлов</returns>
        public NodeCollection<Node> Find(CheckNodeDelegate checkDelegate, bool deep)
        {
            OnDataAsked();
            var nd = new NodeCollection<Node>();
            using (NodeCollection<Node>.NodeCollectionEditingFlag<Node> flag = nd.BeginEdit())
            {
                foreach (Node n in ChildNodes)
                {
                    if (checkDelegate(n))
                        nd.Add(n);
                    if (deep && n.HasChild)
                    {
                        nd.Add(n.Find(checkDelegate, deep));
                    }
                }
            }
            return nd;
        }

        public Node FindFirst(CheckNodeDelegate checkDelegate)
        {
            return FindFirst(checkDelegate, true);
        }

        /// <summary>
        /// Найти все дочерние узлы(поиск происходит по всей части дерева
        /// начинающейся с данного элемента) соответсвующие условию
        /// </summary>
        /// <param name="checkDelegate">Условие</param>
        /// <returns>коллекция узлов</returns>
        public Node FindFirst(CheckNodeDelegate checkDelegate, bool deep)
        {
            OnDataAsked();
            foreach (Node n in ChildNodes)
            {
                if (checkDelegate(n))
                    return n;
                if (deep && n.HasChild)
                {
                    Node node = n.FindFirst(checkDelegate, deep);
                    if (node != null)
                        return node;
                }
            }
            return null;
            
        }

        public NodeCollection<Node> Find(CheckNodeDelegateChain checkDelegate)
        {
            return Find(checkDelegate, true);
        }

        /// <summary>
        /// Найти все дочерние узлы(поиск происходит по всей части дерева
        /// начинающейся с данного элемента) соответсвующие условию. 
        /// </summary>
        /// <param name="checkDelegate">Условие</param>
        /// <returns>коллекция узлов</returns>
        public NodeCollection<Node> Find(CheckNodeDelegateChain checkDelegate, bool deep)
        {
        	checkDelegate.Pop();
            OnDataAsked();
            var nd = new NodeCollection<Node>();
            using (NodeCollection<Node>.NodeCollectionEditingFlag<Node> flag = nd.BeginEdit())
            {
                foreach (Node n in ChildNodes)
                {
                    if (checkDelegate.Check(n))
                    {
                        nd.Add(n);
                        if (deep && n.HasChild&& checkDelegate.IsNonEmpty())
                    	{
                        	nd.Add(n.Find(checkDelegate, deep));
                    	}
                    }
                }
            }
            return nd;
        }

        public event NodeChangedDelegate Changed;

        public event NodeDataAskedDelegate DataAsked;

        protected void OnDataAsked()
        {
            DataAsked(this);
        }

        protected void OnChanged()
        {
            Changed(this);
        }

        #region Nested type: NodeAppender

        ///<summary>
        /// Класс для заполнения свойств класса узла.
        ///</summary>
        public abstract class NodeAppender
        {
            private readonly Node _curNode;
            private readonly List<string> _propertysName = new List<string>();

            public NodeAppender(Node node)
            {
                _curNode = node;
                _curNode.Changed += delegate(Node nd)
                                        {
                                            if (nd == _curNode)
                                            {
                                                NodeChangedHandler();
                                            }
                                        };
                _curNode.DataAsked += delegate(Node nd)
                                          {
                                              if (nd == _curNode)
                                              {
                                                  NodeDataAskedHandler();
                                              }
                                          };
            }

            ///<summary>
            /// Узел к которому применяются свойства
            ///</summary>
            [NonIndex]
            protected Node CurNode
            {
                get { return _curNode; }
            }

            public List<string> PropertysName
            {
                get { return _propertysName; }
            }

            ///<summary>
            /// Функция вызываемая при изменении узла.
            /// </summary>
            protected virtual void NodeChangedHandler()
            {
            }

            /// <summary>
            /// Функция вызываемая при запросе узлом данных
            /// </summary>
            protected virtual void NodeDataAskedHandler()
            {
            }

            protected List<string> EnumeratePropertys()
            {
                foreach (PropertyInfo pm in GetType().GetProperties())
                {
                    bool _continue = false;
                    foreach (object att in (pm.GetCustomAttributes(true)))
                    {
                        if (att != null)
                            if (att is NonIndexAttribute)
                            {
                                _continue = true;
                                break;
                            }
                    }
                    if (_continue)
                        continue;
                    _propertysName.Add(pm.Name);
                }
                return _propertysName;
            }

            protected virtual object GetPropertyByName(string name)
            {
                foreach (PropertyInfo pm in GetType().GetProperties())
                {
                    if (pm.Name == name)
                        return pm;
                }
                throw new ArgumentException("Свойство " + name + " не найдено");
            }

            protected virtual void SetProperyByName(string name, object val)
            {
                foreach (PropertyInfo pm in GetType().GetProperties())
                {
                    if (pm.Name == name && val.GetType() == pm.PropertyType && pm.CanWrite)
                    {
                        pm.SetValue(this, val, BindingFlags.SetProperty, null, null, null);
                        return;
                    }
                }
                throw new ArgumentException("Свойство " + name + " не найдено или его не возможно установить");
            }

            protected abstract void SaveNode();
            protected abstract void FillNode();

            public void Fill()
            {
                FillNode();
            }

            public void Save()
            {
                SaveNode();
            }
        }

        #endregion

        #region Nested type: NodeXmlReader

        public class NodeXmlReader : IReader<XmlReader, Node>
        {
            /// <summary>
            /// Функция для переопределения в дочернем классе
            /// Инициализирует конкретый узел
            /// </summary>
            /// <param name="reader">источник данных</param>
            /// <param name="initedNode">узел для инициализации. Если он ноль то узел надо создать</param>
            /// <returns>инициализированный узел дерева</returns>
            protected virtual Node InitNode(XmlReader reader, Node initedNode)
            {
                if (initedNode == null)
                {
                    initedNode = new Node();
                }
                initedNode._xmlName = reader.LocalName;
                return initedNode;
            }

            /// <summary>
            /// Функция для проверки,
            /// сможет ли данный ридер прочитать заданный Xml узел.
            /// </summary>
            /// <param name="rd">ридер</param>
            /// <param name="_prevRes">результат пердыдущей проверки</param>
            /// <returns>true если сможет</returns>
            protected virtual bool CheckReader(XmlReader rd, bool _prevRes, Node parentNode)
            {
                if (!_prevRes)
                    return false;
                else
                    return true;
            }

            /// <summary>
            /// Функция для инициализации объекта после его полного чтения
            /// </summary>
            /// <param name="result">прочитанный объект</param>
            /// <returns>инициализированный объект</returns>
            protected virtual Node InitObjectAfterBuild(Node result)
            {
                return result;
            }

            #region Члены IReader<XmlReader, Node>

            Node IReader<XmlReader, Node>.Read(XmlReader source)
            {
                Node res = null;
                while (source.Read())
                {
                    if (source.NodeType == XmlNodeType.EndElement)
                    {
                        if (res != null)
                            if (source.LocalName == res._xmlName)
                                break;
                    }
                    else if (source.NodeType == XmlNodeType.Element)
                    {
                        if (res == null)
                        {
                            res = InitNode(source, null);
                        }
                        else
                        {
                            IReader rd = RWHolder.GetInstace().GetReader(typeof (Node), source.ReadSubtree(), res);
                            if (rd != null)
                            {
                                res.AppendChild(rd.Read(source.ReadSubtree()) as Node);
                            }
                            else
                            {
                                //throw 
                            }
                        }
                    }
                }
                return InitObjectAfterBuild(res);
            }

            bool IReader<XmlReader, Node>.CanRead(XmlReader source, Node prevNode)
            {
                return CheckReader(source, true, prevNode);
            }

            #endregion

            #region Члены IReader

            object IReader.Read(object source)
            {
                if (source is XmlReader)
                    return (this as IReader<XmlReader, Node>).Read(source as XmlReader);
                throw new ReadExeption("NodeXmlReader не может читать данные из объекта класса " + source.GetType());
            }


            bool IReader.CanRead(object source, Type destType, Node prevNode)
            {
                if (destType == typeof (Node) && source is XmlReader)
                {
                    if (CheckReader(source as XmlReader, true, prevNode))
                        return true;
                }
                return false;
            }

            #endregion
        }

        #endregion
        #region INewReaderImpl
        public class NodeDataProxyNewReader :INewReader<IDataNode,Node>
        {
            public object Read(IReadEviroment enviroment, object sourceType)
            {
                return Read(enviroment, (IDataNode) sourceType);
            }

            public bool CanRead(object source, Node parent)
            {
                return CanRead((IDataNode) source, parent);
            }
            /// <summary>
            /// Функция для переопределения в дочернем классе
            /// Инициализирует конкретый узел
            /// </summary>
            /// <param name="reader">источник данных</param>
            /// <param name="initedNode">узел для инициализации. Если он ноль то узел надо создать</param>
            /// <returns>инициализированный узел дерева</returns>
            protected virtual Node InitNode(IDataNode reader, Node initedNode)
            {
                if (initedNode == null)
                {
                    initedNode = new Node();
                }
                initedNode._xmlName = reader.Name;
                return initedNode;
            }

            /// <summary>
            /// Функция для проверки,
            /// сможет ли данный ридер прочитать заданный Xml узел.
            /// </summary>
            /// <param name="rd">ридер</param>
            /// <param name="_prevRes">результат пердыдущей проверки</param>
            /// <returns>true если сможет</returns>
            protected virtual bool CheckReader(IDataNode rd, bool _prevRes, Node parentNode)
            {
                if (!_prevRes)
                    return false;
                else
                    return true;
            }
            public Node Read(IReadEviroment enviroment, IDataNode sourceType)
            {
                var res = InitNode(sourceType, null);
                var collection = sourceType as IDataNodeCollection;
                if(collection!=null)
                {
                    collection.Select(node => enviroment.GetReader(node, res).Read(enviroment, node))
                              .Select(node=>res.AppendChild(node));
                }
                return InitObjectAfterBuild(res);
            }

            public bool CanRead(IDataNode source, Node parent)
            {
                return CheckReader(source, true, parent);
            }
            /// <summary>
            /// Функция для инициализации объекта после его полного чтения
            /// </summary>
            /// <param name="result">прочитанный объект</param>
            /// <returns>инициализированный объект</returns>
            protected virtual Node InitObjectAfterBuild(Node result)
            {
                return result;
            }
        }
        #endregion
    }
}